package com.axinfu.util.validq;

import com.axinfu.util.validq.exception.ValidQException;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 验证类
 *
 * @author zjn
 * @since 2022/3/23
 */
@SuppressWarnings("unused")
public class ValidQ {

    /**
     * 上下文
     */
    private ValidatorContext context = new ValidatorContext();

    /**
     * 验证器节点集合
     */
    private List<ValidatorElement> validatorElements = new ArrayList<>();

    /**
     * 验证回调
     */
    private ValidateCallback defaultValidateCallback = new DefaultValidateCallback();

    /**
     * 快速返回（验证失败即返回）
     */
    private boolean returnOnFail = true;

    protected ValidQ() {
    }

    /**
     * 获取一个验证工具
     *
     * @return 验证工具
     */
    public static ValidQ create() {
        return new ValidQ();
    }

    /**
     * 设置参数
     *
     * @param key   参数名称
     * @param value 参数值
     * @return this
     */
    public ValidQ setAttr(String key, Object value) {
        this.context.setAttr(key, value);
        return this;
    }

    /**
     * 设置参数
     *
     * @param values 参数
     * @return this
     */
    public ValidQ setAttr(Map<String, Object> values) {
        this.context.setAttr(values);
        return this;
    }

    /**
     * 设置失败即返回
     *
     * @return this
     */
    public ValidQ failReturn() {
        this.returnOnFail = true;
        return this;
    }

    /**
     * 设置失败继续验证
     *
     * @return this
     */
    public ValidQ failContinue() {
        this.returnOnFail = false;
        return this;
    }

    /**
     * 添加验证器
     *
     * @param target    要验证的值
     * @param validator 验证器
     * @param level     验证层级（数字越小优先级越高，从小到大验证）
     * @param index     验证优先级（数字越小优先级越高，从小到大验证）
     * @param when      是否生效
     * @param <T>       值类型
     * @return this
     */
    public <T> ValidQ on(T target, Validator validator, int level, int index,
                         boolean when) {
        if (when) {
            this.validatorElements.add(new ValidatorElement(target, validator, level, index));
        }
        return this;
    }

    /**
     * 添加验证器
     *
     * @param target    要验证的值
     * @param validator 验证器
     * @param level     验证层级（数字越小优先级越高，从小到大验证）
     * @param index     验证优先级（数字越小优先级越高，从小到大验证）
     * @param <T>       值类型
     * @return this
     */
    public <T> ValidQ on(T target, Validator validator, int level, int index) {
        return on(target, validator, level, index, true);
    }

    /**
     * 添加验证器
     *
     * @param target    要验证的值
     * @param validator 验证器
     * @param index     验证优先级（数字越小优先级越高，从小到大验证）
     * @param when      是否生效
     * @param <T>       值类型
     * @return this
     */
    public <T> ValidQ on(T target, Validator validator, int index, boolean when) {
        return on(target, validator, 1, index, when);
    }

    /**
     * 添加验证器
     *
     * @param target    要验证的值
     * @param validator 验证器
     * @param when      是否生效
     * @param <T>       值类型
     * @return this
     */
    public <T> ValidQ on(T target, Validator validator, boolean when) {
        return on(target, validator, validator.index(), when);
    }

    /**
     * 添加验证器
     *
     * @param target    要验证的值
     * @param validator 验证器
     * @param index     验证优先级（数字越小优先级越高，从小到大验证）
     * @param <T>       值类型
     * @return this
     */
    public <T> ValidQ on(T target, Validator validator, int index) {
        return on(target, validator, index, true);
    }

    /**
     * 添加验证器
     *
     * @param target    要验证的值
     * @param validator 验证器
     * @param <T>       值类型
     * @return this
     */
    public <T> ValidQ on(T target, Validator validator) {
        return on(target, validator, true);
    }

    /**
     * 执行验证
     *
     * @param validateCallback 验证回调
     * @return 验证结果
     */
    public ValidationResult valid(ValidateCallback validateCallback) {
        long start = System.currentTimeMillis();
        try {
            validatorElements.sort((o1, o2) -> {
                if (o1.getLevel() != o2.getLevel()) {
                    return o1.getLevel() - o2.getLevel();
                }
                return o1.getIndex() - o2.getIndex();
            });
            for (ValidatorElement validatorElement : validatorElements) {
                Object target = validatorElement.getTarget();
                Validator v = validatorElement.getValidator();
                try {
                    if (!v.accept(context, target)) {
                        continue;
                    }
                    if (v.validate(context, target)) {
                        continue;
                    }
                    context.getResult().setSuccess(false);
                    context.getResult().setFailure(true);
                    if (returnOnFail) {
                        break;
                    }
                } catch (Exception e) {
                    try {
                        v.onException(e, context, target);
                        validateCallback.onException(v, e, target);
                    } catch (Exception e1) {
                        throw new ValidQException(e1);
                    }
                    throw new ValidQException(e);
                }
            }

            if (context.getResult().isSuccess()) {
                validateCallback.onSuccess(validatorElements);
            } else {
                validateCallback.onFail(validatorElements, context.getResult().getErrors());
            }
        } finally {
            context.getResult().setValidTime(System.currentTimeMillis() - start);
        }
        return context.getResult();
    }

    /**
     * 执行验证
     *
     * @return 验证结果
     */
    public ValidationResult valid() {
        return valid(defaultValidateCallback);
    }

    public ValidatorContext getContext() {
        return context;
    }

    public ValidQ setContext(ValidatorContext context) {
        this.context = context;
        return this;
    }

    public List<ValidatorElement> getValidatorElements() {
        return validatorElements;
    }

    public ValidQ setValidatorElements(List<ValidatorElement> validatorElements) {
        this.validatorElements = validatorElements;
        return this;
    }

    public ValidateCallback getDefaultValidateCallback() {
        return defaultValidateCallback;
    }

    public ValidQ setDefaultValidateCallback(ValidateCallback defaultValidateCallback) {
        this.defaultValidateCallback = defaultValidateCallback;
        return this;
    }

    public boolean isReturnOnFail() {
        return returnOnFail;
    }

    public ValidQ setReturnOnFail(boolean returnOnFail) {
        this.returnOnFail = returnOnFail;
        return this;
    }
}
